﻿using System;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using NHibernate;
using HibernateConfig = NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;

namespace mForum.Infrastructure.NHibernate
{
    using mForum.Core.Common;
    using mForum.Core.Infrastructure.Configuration;

    public class DatabaseFactory : IDisposable, IDatabaseFactory
    {
        private static readonly IDictionary<string, IPersistenceConfigurer> _persistenceConfigurerMap = BuildPersistenceConfigurerMap();
        private static readonly object _sessionFactorySyncLock = new object();

        private IConfigurationWrapper _configurationWrapper;

        private static ISessionFactory _sessionFactory;

        private DatabaseContext _databaseContext;

        public DatabaseFactory(IConfigurationWrapper configurationWrapper)
        {
            Check.Argument.IsNotNull(configurationWrapper, "configurationWrapper");

            _configurationWrapper = configurationWrapper;
        }


        public DatabaseContext Get()
        {
            if (_databaseContext == null)
            {
                EnsureSessionFactory();

                _databaseContext = new DatabaseContext(_sessionFactory.OpenSession());
            }

            return _databaseContext;
        }

        [DebuggerStepThrough]
        public void Dispose()
        {
            if (_databaseContext != null)
            {
                _databaseContext.Dispose();
            }
        }

        private static Dictionary<string, IPersistenceConfigurer> BuildPersistenceConfigurerMap()
        {
            return new Dictionary<string, IPersistenceConfigurer>(StringComparer.OrdinalIgnoreCase)
                       {
                           { "System.Data.SqlClient", MsSqlConfiguration.MsSql2008 },
                           { "MySql.Data.MySqlClient", MySQLConfiguration.Standard }
                       };
        }

        private void EnsureSessionFactory()
        {
            if (_sessionFactory == null)
            {
                lock (_sessionFactorySyncLock)
                {
                    if (_sessionFactory == null)
                    {
                        IPersistenceConfigurer configurer = _persistenceConfigurerMap[_configurationWrapper.ProviderName];

                        MethodInfo setConnectionString = configurer.GetType()
                                                                   .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod)
                                                                   .First(metheod => metheod.Name == "ConnectionString" && metheod.GetParameters().Any() && metheod.GetParameters()[0].ParameterType.Equals(typeof(string)));
                        
                        setConnectionString.Invoke(configurer, new[] { _configurationWrapper.ConnectionString });

                        var cfg = Fluently.Configure()
                                          .Database(configurer)
                                          .Mappings(mapping => mapping.FluentMappings.AddFromAssemblyOf<DatabaseContext>())
                                          .BuildConfiguration();

                        //building database schema in database and output file
                        //BuildSchema(cfg);

                        _sessionFactory = cfg.BuildSessionFactory();
                    }
                }
            }
        }

        private void BuildSchema(HibernateConfig.Configuration config)
        {
            new SchemaExport(config)
                .SetOutputFile(Directory.GetCurrentDirectory() + "databaseSchema.sql")
                .Execute(true, true, false);
        }
    }
}
